package com.entropyfails.wikipedianthewavebot;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.wave.api.AbstractRobotServlet;
import com.google.wave.api.Blip;
import com.google.wave.api.Event;
import com.google.wave.api.EventType;
import com.google.wave.api.Range;
import com.google.wave.api.RobotMessageBundle;
import com.google.wave.api.TextView;
import com.google.wave.api.Wavelet;

import fj.F;  
import fj.data.Array;  
import fj.data.HashMap;

import org.json.*;

@SuppressWarnings("serial")
public class WikipedianTheWavebotServlet extends AbstractRobotServlet  {

	private static final Logger log = 
		Logger.getLogger( WikipedianTheWavebotServlet.class.getName() );

	// Default search key is wikipedia
	private static String SEARCHLOC = "http://en.wikipedia.org/w/api.php?action=query&format=json&titles=";
	private static String WIKILOC = "http://en.wikipedia.org/wiki/";
	// Small word list of 500 most common and outlieres
	static HashMap<String,Integer> small_words = getHashFromFile("common_words.txt");
	static HashMap<String,Integer> exclude_words = null;

	// Process the wiki in single word mode?
	static boolean processSingle = true;

	// Process the wiki at all?
	static boolean quitemode = false;
	
	// Simple event processing loop
	@Override
	public void processEvents(RobotMessageBundle events) {
		exclude_words = small_words;
		//log.warning("evt= " + events.toString());
		Wavelet wavelet = events.getWavelet();

		if (events.wasSelfAdded()) {
			Blip blip = wavelet.appendBlip();
			TextView textView = blip.getDocument();
			textView.append("Wikipedian the WaveBot is here.");
		}
		events.getEvents();
		List<Event> submittedEvents = events.getBlipSubmittedEvents();
		for (Event e : submittedEvents) {
			Blip blip = e.getBlip();
			TextView textView = blip.getDocument();
			//log.warning("Got Blip");
			readCommands(textView);
			if (!quitemode)
				wikipediafy(textView);
		}
	}

	// Read in Wikipedian commands
	void readCommands(TextView textView){
		String text = textView.getText();
		Pattern commands = Pattern.compile("(\\[\\[Wikipedian .*\\]\\])+");
		Matcher m = commands.matcher(text);
		//ArrayList<Range> to_strike  = new ArrayList();
		
		int lost_chars = 0;
		while(m.find()){
			log.warning("found= " + m.group());
			if(m.group().equalsIgnoreCase("[[Wikipedian restore]]")) {
				quitemode = false;
				processSingle = true;
				SEARCHLOC = "http://en.wikipedia.org/w/api.php?action=query&format=json&titles=";
				WIKILOC = "http://en.wikipedia.org/wiki/";
			}
			else if(m.group().equalsIgnoreCase("[[Wikipedian shut it]]"))
				quitemode = true;
			else if(m.group().equalsIgnoreCase("[[Wikipedian chat on]]"))
				quitemode = false;
			else if(m.group().equalsIgnoreCase("[[Wikipedian try1]]"))
				processSingle = true;
			else if (m.group().equalsIgnoreCase("[[Wikipedian try3]]"))
				processSingle = false;
			else if (m.group().substring(0, 15).equalsIgnoreCase("[[Wikipedian s=")) {
				String url = m.group().substring(15);
				SEARCHLOC = url.substring(0, url.length()-2);
			} else if (m.group().substring(0, 15).equalsIgnoreCase("[[Wikipedian w=")) {
				String url = m.group().substring(15);
				WIKILOC = url.substring(0, url.length()-2);
			}
			Range to_delete = new Range(m.start(),m.end());
						
			textView.delete(new Range(m.start()- lost_chars ,m.end() - lost_chars));
			int del_in_round = to_delete.getEnd() - to_delete.getStart(); 
			lost_chars += del_in_round;
		}

	}

	// Choose the version of wikipediafy and go from there.
	void wikipediafy(TextView textView){
		if(processSingle)
			tryLengthOneWiki(textView);
		else
			tryLengthThreeWiki(textView);
	}

	// Simple 3 word wiki. Should be refactored to handle the N case.
	void tryLengthThreeWiki(TextView textView){
		String text = textView.getText();
		final HashMap<String,Integer> seen_words = HashMap.hashMap();
		Pattern words = Pattern.compile("(\\w+)+");
		Matcher m = words.matcher(text);
		String prev_word = null;
		Range prev_range = null;
		String prev_prev_word = null;
		Range prev_prev_range = null;
		while(m.find()){

			if(m.end() > m.start()) {
				Range to_a = new Range(m.start(),m.end());
				String wiki_word = text.substring(to_a.getStart(),to_a.getEnd()).toLowerCase();
				if(prev_word != null && prev_prev_word != null) {
					// Try for a three part wiki word
					String tryThree = prev_prev_word + "_" + prev_word + "_" + wiki_word;
					Range tryThreeRng = new Range(prev_prev_range.getStart(), to_a.getEnd());
					if(tryLink(textView,tryThree,tryThreeRng,seen_words)) {
						wiki_word = null;
						to_a = null;
						prev_word = null;
						prev_range = null;
						prev_prev_word = null;
						prev_prev_range = null;
					} else {
						// Try for a two part wikiword 
						String tryTwo = prev_prev_word + "_" + prev_word;
						Range tryTwoRng = new Range(prev_prev_range.getStart(), prev_range.getEnd());
						if(tryLink(textView,tryTwo,tryTwoRng,seen_words)) {
							prev_word = null;
							prev_range = null;
							prev_prev_word = null;
							prev_prev_range = null;
						} else {
							// try the link that is going to fall off.
							tryLink(textView,prev_prev_word,prev_prev_range,seen_words);
						}
					}
				}
				prev_prev_word = prev_word;
				prev_prev_range = prev_range;
				prev_word = wiki_word;
				prev_range = to_a;
			}
		}

		// Cleanup by checking for a one word only blip or a 2 word blip at the end
		if(prev_prev_word == null) {
			if (prev_word != null)
				tryLink(textView,prev_word,prev_range,seen_words);
		} else {
			String tryTwo = prev_prev_word + "_" + prev_word;
			Range tryTwoRng = new Range(prev_prev_range.getStart(), prev_range.getEnd());
			if(!tryLink(textView,tryTwo,tryTwoRng,seen_words)) {
				tryLink(textView,prev_prev_word,prev_prev_range,seen_words);
				tryLink(textView,prev_word,prev_range,seen_words);
			}
		}

	}
	
	// Loop through one element at a time.
	void tryLengthOneWiki(TextView textView){
		String text = textView.getText();
		final HashMap<String,Integer> seen_words = HashMap.hashMap();
		Pattern words = Pattern.compile("(\\w+)*");
		Matcher m = words.matcher(text);
		while(m.find()){

			if(m.end() > m.start()) {
				Range to_a = new Range(m.start(),m.end());
				String wiki_word = text.substring(to_a.getStart(),to_a.getEnd()).toLowerCase();
				tryLink(textView,wiki_word,to_a,seen_words);
			}
		}
	}
	
	// Attempt to add a word to as a link. Return True if we did add a link annotation or else false.
	boolean tryLink(TextView textView, String word, Range rng, final HashMap<String,Integer> seen_words){
		if(!shouldSkipWord(word,seen_words) && isInWiki(word)) {
			//log.warning("Setting link to="+ word);
			seen_words.set(word, 1);
			textView.setAnnotation(rng, "link/manual", WIKILOC + word);
			return true;
		} 
		return false;
	}

	// Returns true if it should skip a word due to having seen the word before or it being excluded.
	boolean shouldSkipWord(final String word, final HashMap<String,Integer> seen_words){
		
		if (!seen_words.isEmpty() && seen_words.contains(word))
			return true;
		
		if(exclude_words.contains(word) || exclude_words.contains(word + "s") || 
		exclude_words.contains(word + "es") || exclude_words.contains(word + "ing") ||
		exclude_words.contains(word + "ly") || exclude_words.contains(word + "er") ||
		exclude_words.contains(word + "ed") || isANumber(word))
		{
			seen_words.set(word, 1);
			return true;
		}
		return false;
	}	

	// Tests if a word is a number only.
	public boolean isANumber(String word){
		Pattern numbers = Pattern.compile("[0-9]*");
		Matcher m = numbers.matcher(word);
		return m.matches();

	}
	
	// Loads a hashmap from a file for existance testing
	static public HashMap<String,Integer> getHashFromFile(String filename) {
		HashMap<String,Integer> sm = HashMap.hashMap();
		File wordFile = new File(filename);
		try {
			BufferedReader input =  new BufferedReader(new FileReader(wordFile));
			try {
				String line = null; 
				while (( line = input.readLine()) != null){
					sm.set(line.replaceAll("\n",""), 1);
				}
			}
			finally {
				input.close();
			}
		}
		catch (IOException ex) {
			log.warning(ex.toString());
		}
		return sm;
	}

	
	// Checks to see if a word or words with "_" are in the linked wiki.
	boolean isInWiki(String possible_word) {

		JSONObject search_return;
		try {
			search_return = readJSONFromWiki(possible_word);
			JSONObject look_for_missing = search_return.getJSONObject("query").getJSONObject("pages");
			for(Iterator<String> k = look_for_missing.keys(); k.hasNext();) {
				if(look_for_missing.getJSONObject(k.next()).has("missing"))
					return false;
				else
					return true;
			}
			return false;
		} catch (IOException e) {
			log.warning(e.toString());
			return false;
		} catch (JSONException e) {
			log.warning(e.toString());
			return false;
		}

	}
	
	
	// Get the json back from the SEARCHLOC url and pass it as a json object.
	JSONObject readJSONFromWiki(String possible_word) throws IOException , JSONException { 
		
		String total_file = "";
		try {
			URL search_site;
			search_site = new URL(SEARCHLOC + possible_word);
			BufferedReader in = new BufferedReader(
					new InputStreamReader(
							search_site.openStream()));
			String inputLine;
			while ((inputLine = in.readLine()) != null)
				total_file += inputLine + "\n";

			in.close();
		} catch (MalformedURLException e) {
			log.warning(e.toString());
		}
		return new JSONObject(total_file);
	}

}
